/** @file main_window.cpp
    @brief Main window functions */
#include "main_window.h"

#include <QStandardItemModel>
#include <QFileDialog>
#include <QString>

MainWindow::MainWindow( QWidget *parent ) :
    QMainWindow( parent )
{
    m_num_screenshots = 0;

    m_signal_mapper = NULL;

    m_status = NULL;
}

void MainWindow::init() {
    pm3::PIXELWORLD *world = pm3::PIXELWORLD::getSingletonPtr();

    // Setup UI
    setupUi( this );
    // Setup actions
    setupActions();
    // Init render window
    renderWindow->init();

    // Add stuff to the statusbar
    m_status = new QLabel( statusbar );
    statusbar->addPermanentWidget( m_status );

    // Add dummy colors
    add_color( Ogre::ColourValue( 0, 0, 0, 1 ) );
    add_color( Ogre::ColourValue( 1, 1, 1, 1 ) );
    add_color( Ogre::ColourValue( 0, 1, 1, 1 ) );
    add_color( Ogre::ColourValue( 1, 1, 0, 1 ) );
    add_color( Ogre::ColourValue( 1, 0, 1, 1 ) );

    // Set player mouse sensitivity
    player_mouse_sens->setValue( world->m_player.get_mouse_sensitivity() );
    // Set player movement speed
    player_mov_speed->setValue( world->m_player.get_movement_speed() );
    // Set player collision radius
    player_radius->setValue( world->m_player.get_radius() );
    // Set player collision height
    player_height->setValue( world->m_player.get_height() );
    // Set player mass
    player_mass->setValue( world->m_player.get_mass() );
    // Set player tags
    player_tags->setPlainText( world->m_player.get_tags().c_str() );

    update_status();
}

void MainWindow::setupActions() {
    // Create a new signal mapper
    m_signal_mapper = new QSignalMapper( this );

    QObject::connect( actionNew_map, SIGNAL( triggered() ), this, SLOT( new_map() ) );
    QObject::connect( actionLoad_map, SIGNAL( triggered() ), this, SLOT( load_map() ) );
    QObject::connect( actionSave_map, SIGNAL( triggered() ), this, SLOT( save_map() ) );

    QObject::connect( actionGod_mode, SIGNAL( toggled( bool ) ), this, SLOT( toggle_god_mode( bool ) ) );
    QObject::connect( actionPhysics_debug, SIGNAL( toggled( bool ) ), this, SLOT( toggle_physics_debug( bool ) ) );
    QObject::connect( actionPortals_debug, SIGNAL( toggled( bool ) ), this, SLOT( toggle_portals_debug( bool ) ) );

    QObject::connect( room_size, SIGNAL( valueChanged( double ) ), this, SLOT( roomsize_changed( double ) ) );

    QObject::connect( layer_spin, SIGNAL( valueChanged( int ) ), this, SLOT( layer_changed( int ) ) );
    QObject::connect( actionLayer_up, SIGNAL( triggered() ), this, SLOT( layer_up() ) );
    QObject::connect( actionLayer_down, SIGNAL( triggered() ), this, SLOT( layer_down() ) );
    QObject::connect( add_layer_button, SIGNAL( clicked() ), this, SLOT( layer_add() ) );
    QObject::connect( remove_layer_button, SIGNAL( clicked() ), this, SLOT( layer_remove() ) );

    QObject::connect( draw_tool, SIGNAL( clicked() ), this, SLOT( start_drawing() ) );
    QObject::connect( actionDraw, SIGNAL( triggered() ), this, SLOT( start_drawing() ) );
    QObject::connect( select_tool, SIGNAL( clicked() ), this, SLOT( start_selection() ) );
    QObject::connect( actionSelect, SIGNAL( triggered() ), this, SLOT( start_selection() ) );

    QObject::connect( actionObjects, SIGNAL( triggered() ), this, SLOT( objects_dialog() ) );

    QObject::connect( renderWindow, SIGNAL( room_picked( pm3::PIXELROOM * ) ), this, SLOT( room_picked( pm3::PIXELROOM * ) ) );

    // Connect the signal mapper
    QObject::connect( m_signal_mapper, SIGNAL( mapped( QString ) ), this, SLOT( mapped_slot( QString ) ) );
}

void MainWindow::toggle_god_mode( bool b_mode ) {
    pm3::PIXELWORLD *world = pm3::PIXELWORLD::getSingletonPtr();
    pm3::PIXELLAYER *layer = NULL;

    if (!world)
        return;

    world->m_player.set_god_mode( b_mode );

    //! \bug Might not work when player is floating out of the layer
    layer = world->m_map.get_layer( world->m_player.get_world_pos() );

    if (layer) {
        if (b_mode) {
            // Hide the whole map
            world->m_map.set_render_mode( pm3::RM_HIDE );
            // And only show the active layer
            layer->set_render_mode( pm3::RM_SHOW );
            // And the roomboxes of that layer
            layer->show_debug_boxes( true );
        } else {
            // Set the visibility of the whole map to be determined by portals
            world->m_map.set_render_mode( pm3::RM_PVS );
        }
    }
}

void MainWindow::toggle_physics_debug( bool b_mode ) {
    pm3::PHYSICS *physics = pm3::PHYSICS::getSingletonPtr();

    if (!physics)
        return;

    physics->get_physics_debug()->setDebugMode( b_mode );
}

void MainWindow::toggle_portals_debug( bool b_mode ) {
    pm3::dbg::PORTAL_DRAWER::getSingleton().set_enabled( b_mode );
}

void MainWindow::keyPressEvent( QKeyEvent *p_event ) {
    pm3::LOGGER *log = pm3::LOGGER::getSingletonPtr();
    pm3::INTERFACE *intf = pm3::INTERFACE::getSingletonPtr();
    static pm3::FRAME_LISTENER *framelistener = NULL;
    std::ostringstream ss;

    if (renderWindow && framelistener == NULL)
        framelistener = renderWindow->get_framelistener();

    // Update framelistener keymap
    framelistener->m_keymap [p_event->key()] = true;

    switch( p_event->key() ) {
        // Take a screenshot
        case Qt::Key_SysReq:
			ss << "screenshot_" << ++m_num_screenshots << ".png";

            if (intf->get_render_window()) {
    			intf->get_render_window()->writeContentsToFile( ss.str() );

                if (log)
                    log->report( "All", "Screenshot taken\n" );
            }
            break;

        default: break;
    }
}

void MainWindow::keyReleaseEvent( QKeyEvent *p_event ) {
    static pm3::FRAME_LISTENER *framelistener = NULL;

    if (renderWindow && framelistener == NULL)
        framelistener = renderWindow->get_framelistener();

    // Update framelistener keymap
    framelistener->m_keymap [p_event->key()] = false;
}

void MainWindow::new_map() {
    pm3::LOGGER *log = pm3::LOGGER::getSingletonPtr();
    pm3::PIXELWORLD *world = pm3::PIXELWORLD::getSingletonPtr();
    int layer_id = 0;

    if (log)
        log->report( "Editor", "Creating a new map..\n" );

    // Unload any previous maps
    world->m_map.unload();
    // Create a new layer
    layer_id = world->m_map.new_layer();
    // Show the whole layer
    world->m_map.get_layer( layer_id )->set_render_mode( pm3::RM_SHOW );
}

void MainWindow::add_color( const Ogre::ColourValue &r_col ) {
    QPushButton *button = NULL;
    std::string colstr;
    int x = 0, y = 0;

    // Button name
    std::string str = "rcol";
    str += pm3::util::to_str( m_colorbuttons.size() );
    // Create button
    button = new QPushButton( page_layer );
    button->setObjectName( QString::fromUtf8( str.c_str() ) );
    // Set size policy
    QSizePolicy sizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
    sizePolicy.setHorizontalStretch( 0 );
    sizePolicy.setVerticalStretch( 0 );
    sizePolicy.setHeightForWidth( button->sizePolicy().hasHeightForWidth() );
    button->setSizePolicy( sizePolicy );
    // Set stylesheet
    colstr = pm3::util::to_str( (int) ( r_col.r * 255 ) ) + ", ";
    colstr += pm3::util::to_str( (int) ( r_col.g * 255 ) ) + ", ";
    colstr += pm3::util::to_str( (int) ( r_col.b * 255 ) );

    str = "background-color: rgb(";
    str += colstr + ");";

    button->setStyleSheet( QString::fromUtf8( str.c_str() ) );

    // Set button coordinates
    x = m_colorbuttons.size() % 3;
    y = m_colorbuttons.size() / 3;
    // Add the button to the grid
    rcol_grid->addWidget( button, y, x, 1, 1 );
    // Add the button to the list
    m_colorbuttons.push_back( button );

    // Signal-map the button
    colstr = std::string( "color(" ) + colstr + ")";
    m_signal_mapper->setMapping( button, QString::fromUtf8( colstr.c_str() ) );
    // Connect the button click signal
    QObject::connect( button, SIGNAL( clicked() ), m_signal_mapper, SLOT( map() ) );
}

void MainWindow::add_colors() {
    pm3::PIXELWORLD *world = pm3::PIXELWORLD::getSingletonPtr();
    int colors = world->m_map.num_colors();

    // Add a color for clearing pixels
    add_color( Ogre::ColourValue( 0, 0, 0, 1 ) );

    // Add all colors
    for (int i=0; i<colors; i++)
        add_color( world->m_map.get_color( i ) );
}

void MainWindow::clear_colors() {
    QLayoutItem *lit = NULL;
    QPushButton *pb = NULL;
    int width = rcol_grid->columnCount();
    int height = rcol_grid->rowCount();
    int x, y;

    for (y=0; y<height; y++) {
        for (x=0; x<width; x++) {
            lit = rcol_grid->itemAtPosition( y, x );

            if (lit) {
                // Get widget pointer
                pb = (QPushButton *) lit->widget();
                // Remove button mappings
                m_signal_mapper->removeMappings( pb );

                // Remove from the layout
                rcol_grid->removeItem( lit );

                delete pb;
            }
        }
    }

    m_colorbuttons.clear();
}

void MainWindow::setupLayerList() {
    QStandardItemModel *model = ( QStandardItemModel * ) layer_list->model();

    if (!model)
        model = new QStandardItemModel( 0, 2 );

    model->setHeaderData( 0, Qt::Horizontal, QObject::tr("ID") );
    model->setHeaderData( 1, Qt::Horizontal, QObject::tr("Script") );

    layer_list->setModel( model );
    layer_list->setColumnWidth( 0, 40 );
}

void MainWindow::add_layer_to_list( int i_layer ) {
    pm3::PIXELWORLD *world = pm3::PIXELWORLD::getSingletonPtr();
    pm3::PIXELLAYER *layer = world->m_map.get_layer( i_layer );
    QStandardItemModel *model = ( QStandardItemModel * ) layer_list->model();
    QList< QStandardItem * > list;

    if (!layer || !model)
        return;

    list.append( new QStandardItem( pm3::util::to_str( i_layer ).c_str() ) );
    list.append( new QStandardItem( pm3::util::strip_dir( layer->get_script(), layer->get_directory() ).c_str() ) );

    model->appendRow( list );
}

void MainWindow::add_layers_to_list() {
    pm3::PIXELWORLD *world = pm3::PIXELWORLD::getSingletonPtr();
    int num_layers = world->m_map.num_layers();

    for (int i=0; i<num_layers; i++)
        add_layer_to_list( i );
}

void MainWindow::clear_layerlist() {
    QStandardItemModel *model = ( QStandardItemModel * ) layer_list->model();

    if (model)
        model->clear();

    setupLayerList();
}

void MainWindow::updateWidgets() {
    pm3::PIXELWORLD *world = pm3::PIXELWORLD::getSingletonPtr();

    // Set room size
    room_size->setValue( world->m_map.get_room_size() );

    // Set layer spinbox maximum as appropriate
    layer_spin->setMaximum( world->m_map.num_layers() - 1 );

    // Set layerscript path
    layerscript_path->setText( pm3::lua::g_active_layer->get_script().c_str() );
    // Set layerimage path
    layer_image_path->setText( pm3::lua::g_active_layer->get_image_path().c_str() );

    // Update colors
    clear_colors();
    add_colors();

    // Update layerlist
    clear_layerlist();
    add_layers_to_list();

    update_status();
}

void MainWindow::load_map() {
    pm3::PIXELWORLD *world = pm3::PIXELWORLD::getSingletonPtr();

    // Bring a file open dialog
    QDir dir( QDir::current() );
    QFileDialog qfile;
    qfile.setOption( QFileDialog::DontUseNativeDialog, true );
    qfile.setDirectory( dir );
    QString path = qfile.getOpenFileName();

    // Leave on empty path
    if (path.isEmpty())
      return;

    // Convert the path to canonical / relative form
    path = dir.relativeFilePath( path );

    // Add & select the map
    world->select_map( world->add_map( path.toStdString() ) );

    // Set player into God mode by default
    toggle_god_mode( true );

    // Select the first layer
    layer_changed( 0 );

    // Update widgets
    updateWidgets();
}

void MainWindow::save_map() {
    pm3::LOGGER *log = pm3::LOGGER::getSingletonPtr();
    pm3::PIXELWORLD *world = pm3::PIXELWORLD::getSingletonPtr();
    pm3::PIXELLAYER *layer = NULL;
    pm3::PIXELOBJ *obj = NULL;
    int i, num = 0;

    if (log)
        log->report( "Editor", "I: Saving project..\n" );

    // Save mapscript
    if (world->m_map.is_unsaved()) {
        if (log)
            log->report( "Editor", "I: Saving mapscript \"%s\"\n", world->m_map.get_script().c_str() );
        world->m_map.save_script();
    }

    // Save layers
    num = world->m_map.num_layers();
    for (i=0; i<num; i++) {
        layer = world->m_map.get_layer( i );

        if (layer) {
            // Save layerscript
            if (layer->is_unsaved()) {
                if (log)
                    log->report( "Editor", "I: Saving layerscript \"%s\"\n", layer->get_script().c_str() );
                layer->save_script();
            }
            // Save layer image
            if (layer->is_image_unsaved()) {
                if (log)
                    log->report( "Editor", "I: Saving layer image \"%s\"\n", layer->get_image_path().c_str() );
                layer->save_image();
            }
        }
    }

    // Save objects
    num = world->m_map.num_objects();
    for (i=0; i<num; i++) {
        obj = world->m_map.get_obj( i );

        if (obj) {
            // Save objectscript
            if (obj->is_unsaved()) {
                if (log)
                    log->report( "Editor", "I: Saving objectscript \"%s\"\n", obj->get_script().c_str() );
                obj->save_script();
            }
        }
    }
}

void MainWindow::roomsize_changed( double f_size ) {
    pm3::PIXELWORLD *world = pm3::PIXELWORLD::getSingletonPtr();

    world->m_map.set_room_size( f_size );

    //! \todo Make it work with an already existing map as well    
}

void MainWindow::layer_changed( int i_layer ) {
    pm3::PIXELWORLD *world = pm3::PIXELWORLD::getSingletonPtr();

    // Hide the previous layer
    if (pm3::lua::g_active_layer) {
        pm3::lua::g_active_layer->set_render_mode( pm3::RM_HIDE );
        pm3::lua::g_active_layer->show_debug_boxes( false );
    }

    // Activate the new layer
    pm3::lua::g_active_layer = world->m_map.get_layer( i_layer );

    // Show the new layer
    pm3::lua::g_active_layer->set_render_mode( pm3::RM_SHOW );
    pm3::lua::g_active_layer->show_debug_boxes( true );

    // Set layerscript path
    layerscript_path->setText( pm3::lua::g_active_layer->get_script().c_str() );
    // Set layerimage path
    layer_image_path->setText( pm3::lua::g_active_layer->get_image_path().c_str() );

    update_status();
}

void MainWindow::layer_up() {
    layer_spin->setValue( layer_spin->value() + 1 );
}

void MainWindow::layer_down() {
    layer_spin->setValue( layer_spin->value() - 1 );
}

void MainWindow::layer_add() {
    pm3::PIXELWORLD *world = pm3::PIXELWORLD::getSingletonPtr();
    int layer_id;

    // Create a new layer
    layer_id = world->m_map.new_layer();

    // Set layer spinbox maximum as appropriate
    layer_spin->setMaximum( world->m_map.num_layers() - 1 );
    // Activate the layer
    layer_spin->setValue( layer_id );
}

void MainWindow::layer_remove() {
    //! \todo Think through
}

void MainWindow::mapped_slot( const QString &r_str ) {
    pm3::LOGGER *log = pm3::LOGGER::getSingletonPtr();
    QByteArray bytes = r_str.toAscii();
    std::string str( bytes.data() );
    std::vector< std::string > strvec;
    std::vector< float > fvec;

    if (log)
        log->report( "Editor", "I: Mapped signal \"%s\"\n", bytes.data() );

    if (str.compare( 0, 6, "color(" ) == 0) {
        // Tokenize
        strvec = pm3::util::tokenize( str, "(, )" );

        // Convert to floats
        fvec.clear();
        for (size_t i=1; i<strvec.size(); i++) {
            fvec.push_back( pm3::util::str_to_int( strvec [i] ) );
            fvec [fvec.size() - 1] /= 255.0f;
        }

        // Set paint color
        renderWindow->set_paint_color( Ogre::ColourValue( fvec [0], fvec [1], fvec [2], 1 ) );
    }

    update_status();
}

void MainWindow::start_drawing() {
    renderWindow->set_tool( RenderWindow::T_DRAW );
    actionSelect->setChecked( false );
    actionMove->setChecked( false );

    update_status();
}

void MainWindow::start_selection() {
    renderWindow->set_tool( RenderWindow::T_SELECT );
    actionDraw->setChecked( false );
    actionMove->setChecked( false );

    update_status();
}

void MainWindow::room_picked( pm3::PIXELROOM *p_room ) {
    pm3::LOGGER *log = pm3::LOGGER::getSingletonPtr();
    pm3::PIXELOBJ *obj = NULL;
    Ogre::ColourValue col;
    Ogre::Vector3 v;
    std::string str;

    if (p_room) {
        if (log)
            log->report( "Editor", "I: Picked room \"%s\"\n", p_room->get_name().c_str() );

        // Set room grid position
        v = p_room->get_grid_pos();
        room_grid_x->setText( pm3::util::to_str( v.x ).c_str() );
        room_grid_y->setText( pm3::util::to_str( v.y ).c_str() );
        room_grid_z->setText( pm3::util::to_str( v.z ).c_str() );

        // Get room object
        obj = p_room->get_obj();
        // Is there an object?
        if (obj) {
            // Get its color
            col = obj->get_color();
        }

        // Set stylesheet
        str = "background-color: rgb(";
        str += pm3::util::to_str( (int) ( col.r * 255 ) ) + ", ";
        str += pm3::util::to_str( (int) ( col.g * 255 ) ) + ", ";
        str += pm3::util::to_str( (int) ( col.b * 255 ) ) + ");";

        room_color->setStyleSheet( QString::fromUtf8( str.c_str() ) );

        if (obj) {
            // Set exits
            str = pm3::util::directions_to_str( obj->get_directions() );
            room_exits->setText( str.c_str() );

            // Set objectscript
            room_object->setText( obj->get_script().c_str() );

            // Set tags
            room_tags->setPlainText( obj->get_tags().c_str() );
        } else {
            // Set exits
            room_exits->setText( "" );

            // Set objectscript
            room_object->setText( "" );

            // Set tags
            room_tags->setPlainText( "" );
        }
    }
}

void MainWindow::update_status() {
    RenderWindow::TOOL tool;
    Ogre::ColourValue col;
    std::string str;
    std::string td_style;

    if (!m_status || !renderWindow)
        return;

    td_style = "padding-left: 2px; padding-right: 2px;";

    // Start a table
    str += "<table border=\"1\" cellspacing=\"0\" style=\"border-style: inset;\"><tr>";

    // Indicate tool status
    str += "<td style=\"";
    str += td_style + "\">Tool: ";

    tool = renderWindow->get_tool();
    switch( tool ) {
        case RenderWindow::T_DRAW:
            str += "Draw";
            break;

        case RenderWindow::T_SELECT:
            str += "Select";
            break;

        case RenderWindow::T_MOVE:
            str += "Move";
            break;

        default:
            str += "None";
            break;
    }

    // Indicate color status (if drawing)
    if (tool == RenderWindow::T_DRAW) {
        str += "</td><td width=16 style=\"background-color: #";
        str += pm3::util::color_to_hex( renderWindow->get_paint_color() ) + "; ";
        str += td_style + "\">";
    }

    // Indicate layer status
    str += "</td><td style=\"";
    str += td_style + "\">Layer: ";
    if (pm3::lua::g_active_layer)
        str += pm3::util::to_str( pm3::lua::g_active_layer->get_id() );
    else
        str += "None";

    // End table
    str += "</td></tr></table>";

    // Set status
    m_status->setText( str.c_str() );
}

void MainWindow::objects_dialog() {
    ObjectsDialog *dlg = new ObjectsDialog( this );
    dlg->init();
    dlg->show();
}

